import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { exec, ExecException } from 'child_process';

// Stores the currently selected scene for the active editor
let currentSceneByEditor: Map<string, string | undefined> = new Map();
// Stores the webview panel
let previewPanel: vscode.WebviewPanel | undefined = undefined;
// Stores the details of the last render for re-rendering
let lastRenderArgs: {
    editorPath: string;
    sceneName: string;
    quality: string;
    isImage: boolean;
} | undefined = undefined;


/**
 * Activates the extension.
 * This function is called when any of the activation events defined in package.json occur.
 * @param context The extension context provided by VS Code.
 */
export function activate(context: vscode.ExtensionContext) {
    // Command to select a Manim scene from the current Python file
    context.subscriptions.push(
        vscode.commands.registerCommand('manim-liveview.selectScene', async () => {
            const editor = vscode.window.activeTextEditor;
            if (editor && editor.document.languageId === 'python') {
                const scenes = parseScenes(editor.document.getText());
                if (scenes.length === 0) {
                    vscode.window.showInformationMessage('No Manim scenes found in the current file.');
                    return;
                }
                const selectedScene = await vscode.window.showQuickPick(scenes, {
                    placeHolder: 'Select a Manim scene to render',
                });
                if (selectedScene) {
                    currentSceneByEditor.set(editor.document.uri.fsPath, selectedScene);
                    vscode.window.showInformationMessage(`Selected scene: ${selectedScene}`);
                    // If preview panel is open and showing this file, update the scene name display
                    if (previewPanel && lastRenderArgs && lastRenderArgs.editorPath === editor.document.uri.fsPath) {
                       updatePreviewPanelContent({ sceneName: selectedScene });
                    }
                }
            } else {
                vscode.window.showWarningMessage('Please open a Python file to select a Manim scene.');
            }
        })
    );

    // Command to render the selected Manim scene and show it in a preview panel
    context.subscriptions.push(
        vscode.commands.registerCommand('manim-liveview.renderAndPreviewScene', async () => {
            const editor = vscode.window.activeTextEditor;
            if (!editor || editor.document.languageId !== 'python') {
                vscode.window.showWarningMessage('Please open a Python file to render a Manim scene.');
                return;
            }

            const filePath = editor.document.uri.fsPath;
            let sceneName = currentSceneByEditor.get(filePath);

            if (!sceneName) {
                // If no scene is selected for this file, try to select one
                await vscode.commands.executeCommand('manim-liveview.selectScene');
                sceneName = currentSceneByEditor.get(filePath); // Get it again after selection
                if (!sceneName) {
                    vscode.window.showErrorMessage('No scene selected. Please select a scene first.');
                    return;
                }
            }

            const config = vscode.workspace.getConfiguration('manim-liveview');
            const quality = config.get<string>('defaultQuality', 'h');
            const manimExecutable = config.get<string>('manimExecutablePath', 'manim');
            const renderAsImage = config.get<boolean>('renderAsImage', false);

            lastRenderArgs = { editorPath: filePath, sceneName, quality, isImage: renderAsImage };
            executeManimRender(filePath, sceneName, quality, manimExecutable, renderAsImage, context);
        })
    );

    // Listen for changes in the active text editor
    context.subscriptions.push(
        vscode.window.onDidChangeActiveTextEditor(editor => {
            if (editor && previewPanel && lastRenderArgs) {
                // If the new active editor is the one that was rendered, ensure panel is relevant
                // Or, clear/update panel if it's a different file.
                // For now, let's assume the panel stays and user re-renders for new file.
            }
        })
    );

    // Clean up the panel when it's disposed
    // This check is actually not needed here, onDidDispose should be set when panel is created
    // if (previewPanel) {
    //     previewPanel.onDidDispose(() => {
    //         previewPanel = undefined;
    //         lastRenderArgs = undefined;
    //     }, null, context.subscriptions);
    // }
}

/**
 * Parses Manim scene class names from Python code.
 * This is a simple regex-based parser. For more robustness, an AST parser would be better.
 * @param code The Python code as a string.
 * @returns An array of scene names.
 */
function parseScenes(code: string): string[] {
    const scenes: string[] = [];
    // Regex to find classes inheriting from Scene (and its variants like ThreeDScene)
    // Example: class MyScene(Scene):
    const sceneRegex = /class\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(\s*(?:ThreeDScene|Scene|MovingCameraScene|ZoomedScene)\s*\)\s*:/g;
    let match;
    while ((match = sceneRegex.exec(code)) !== null) {
        scenes.push(match[1]);
    }
    return scenes;
}

/**
 * Executes the Manim render command.
 * @param filePath Absolute path to the Python file.
 * @param sceneName Name of the scene to render.
 * @param quality Quality flag (l, m, h, k).
 * @param manimExecutable Path to the Manim executable.
 * @param isImage True to render as image (save last frame), false for video.
 * @param context Extension context for webview resources.
 */
function executeManimRender(
    filePath: string,
    sceneName: string,
    quality: string,
    manimExecutable: string,
    isImage: boolean,
    context: vscode.ExtensionContext
) {
    const startTime = Date.now();
    const renderQualityFlag = quality; // l, m, h, k

    const imageFlag = isImage ? "-s" : "";
    const command = `"${manimExecutable}" render "${filePath}" ${sceneName} -q${renderQualityFlag} ${imageFlag} --progress_bar none`;

    vscode.window.showInformationMessage(`Rendering scene: ${sceneName} with quality: ${quality}...`);
    
    const terminal = vscode.window.createTerminal("Manim Render");
    terminal.sendText(command);
    terminal.show();

    exec(command, { cwd: path.dirname(filePath) }, (error: ExecException | null, stdout: string, stderr: string) => {
        const renderTime = ((Date.now() - startTime) / 1000).toFixed(2) + 's';

        if (error) {
            console.error(`Manim execution error: ${error.message}`);
            vscode.window.showErrorMessage(`Manim error: ${stderr || error.message}`);
            updatePreviewPanelContent({ // Update panel even on error
                error: `Manim execution failed. Check terminal for details. STDERR: ${stderr || error.message}`,
                renderTime,
                sceneName,
                sourceFilePath: filePath, // filePath is string here
                currentQuality: quality
            });
            return;
        }

        if (stderr && !stdout.includes("File ready at")) {
            console.warn(`Manim stderr: ${stderr}`);
        }
        
        console.log(`Manim stdout: ${stdout}`);

        const filePathRegex = /File ready at:\s*(.*)/;
        const filePathMatch = stdout.match(filePathRegex);
        
        let outputFilePath: string | undefined = undefined;

        if (filePathMatch && filePathMatch[1]) {
            outputFilePath = filePathMatch[1].trim();
        } else {
            console.warn("Could not parse 'File ready at' from Manim output. Output path might be missing.");
            vscode.window.showWarningMessage("Could not reliably determine Manim output file path from stdout.");
        }

        if (outputFilePath && !fs.existsSync(outputFilePath)) {
             vscode.window.showErrorMessage(`Manim reported file ready at "${outputFilePath}", but it was not found.`);
             updatePreviewPanelContent({ // Update panel even on this error
                error: `Output file not found: ${outputFilePath}`,
                renderTime,
                sceneName,
                sourceFilePath: filePath, // filePath is string here
                currentQuality: quality
            });
            return;
        }
        
        vscode.window.showInformationMessage(`Scene ${sceneName} rendered in ${renderTime}.`);

        if (outputFilePath) {
             createOrUpdatePreviewPanel(context, {
                outputFilePath: outputFilePath,
                outputType: outputFilePath.endsWith('.mp4') ? 'video' : (outputFilePath.endsWith('.png') ? 'image' : 'unknown'),
                renderTime: renderTime,
                sceneName: sceneName,
                sourceFilePath: filePath, // filePath is string here
                currentQuality: quality
            });
        } else {
             // This case means rendering might have succeeded but we couldn't find the file path
             updatePreviewPanelContent({
                error: 'Could not determine output file path from Manim. Check Manim output in terminal.',
                renderTime,
                sceneName,
                sourceFilePath: filePath, // filePath is string here
                currentQuality: quality
            });
        }
    });
}

/**
 * Creates a new webview panel for Manim preview or reveals an existing one.
 * @param context The extension context.
 * @param data Data to populate the preview. `data.sourceFilePath` is expected to be defined if creating a new panel.
 */
function createOrUpdatePreviewPanel(context: vscode.ExtensionContext, data: PreviewData) {
    const column = vscode.window.activeTextEditor
        ? vscode.ViewColumn.Beside
        : vscode.ViewColumn.One;

    if (previewPanel) {
        previewPanel.reveal(column);
    } else {
        // Panel is being created for the first time.
        // data.sourceFilePath is critical for localResourceRoots to allow loading media from the workspace.
        // It should be provided by executeManimRender after a successful setup.
        if (!data.sourceFilePath) {
            // This is a critical issue if it happens during initial panel creation.
            console.error("Manim LiveView: sourceFilePath is undefined during initial panel creation. Media from the workspace may not load correctly.");
            vscode.window.showErrorMessage("Manim LiveView: Internal error - source file path missing for preview setup. Cannot create preview panel.");
            return; // Exit if sourceFilePath is missing, as it's essential.
        }

        previewPanel = vscode.window.createWebviewPanel(
            'manimLivePreview', // Identifies the type of the webview. Used internally
            'Manim Preview', // Title of the panel displayed to the user
            column, // Editor column to show the new webview panel in.
            {
                enableScripts: true, // Allow scripts to run in the webview
                // Restrict the webview to only loading content from specified roots.
                localResourceRoots: [
                    vscode.Uri.joinPath(context.extensionUri, 'media'), // For extension's own assets (if any, e.g. global CSS)
                    vscode.Uri.file(path.dirname(data.sourceFilePath))  // For workspace media relative to the script. data.sourceFilePath is now confirmed to be a string.
                ]
            }
        );

        previewPanel.onDidDispose(() => {
            previewPanel = undefined;
            lastRenderArgs = undefined; // Clear last render args when panel is closed
        }, null, context.subscriptions);

        // Handle messages from the webview (e.g., quality change)
        previewPanel.webview.onDidReceiveMessage(
            async message => {
                switch (message.command) {
                    case 'qualityChanged':
                        if (lastRenderArgs) {
                            const newQuality = message.quality;
                            // Optionally update global setting
                            // vscode.workspace.getConfiguration('manim-liveview').update('defaultQuality', newQuality, vscode.ConfigurationTarget.Global);
                            
                            lastRenderArgs.quality = newQuality; // Update quality for next direct render

                            const config = vscode.workspace.getConfiguration('manim-liveview');
                            const manimExecutable = config.get<string>('manimExecutablePath', 'manim');
                            const renderAsImage = config.get<boolean>('renderAsImage', false);

                            executeManimRender(
                                lastRenderArgs.editorPath,
                                lastRenderArgs.sceneName,
                                newQuality,
                                manimExecutable,
                                renderAsImage,
                                context
                            );
                        }
                        return;
                    case 'rerenderRequested':
                         if (lastRenderArgs) {
                            const config = vscode.workspace.getConfiguration('manim-liveview');
                            const manimExecutable = config.get<string>('manimExecutablePath', 'manim');
                            const renderAsImage = config.get<boolean>('renderAsImage', false);
                            executeManimRender(
                                lastRenderArgs.editorPath,
                                lastRenderArgs.sceneName,
                                lastRenderArgs.quality, // Use the stored quality
                                manimExecutable,
                                renderAsImage,
                                context
                            );
                        }
                        return;
                }
            },
            undefined,
            context.subscriptions
        );
    }
    // Set the HTML content for the webview panel (whether new or existing)
    updatePreviewPanelContent(data);
}

interface PreviewData {
    outputFilePath?: string;
    outputType?: 'video' | 'image' | 'unknown';
    renderTime?: string;
    sceneName?: string;
    sourceFilePath?: string; // Path to the source Python script
    error?: string;
    currentQuality?: string;
}

/**
 * Updates the content of the existing preview panel.
 * This function is called to refresh the webview's HTML.
 * @param data Data to populate/update the preview.
 */
function updatePreviewPanelContent(data: PreviewData) {
    if (!previewPanel) {
        // If an error occurred during panel creation (e.g. missing sourceFilePath),
        // previewPanel might be undefined here.
        return;
    }

    // Update lastRenderArgs if essential info is present from a successful render or update
    // This ensures that if the user changes quality or re-renders, it uses the latest valid context.
    if (data.sourceFilePath && data.sceneName && data.currentQuality && !data.error) {
        const config = vscode.workspace.getConfiguration('manim-liveview');
        const renderAsImage = config.get<boolean>('renderAsImage', false);
        lastRenderArgs = {
            editorPath: data.sourceFilePath,
            sceneName: data.sceneName,
            quality: data.currentQuality,
            isImage: renderAsImage
        };
    } else if (data.sourceFilePath && data.sceneName && data.currentQuality && data.error) {
        // Even if there's an error, if we have sourceFilePath, sceneName, and quality,
        // it's useful to keep them in lastRenderArgs for a potential re-render attempt.
        const config = vscode.workspace.getConfiguration('manim-liveview');
        const renderAsImage = config.get<boolean>('renderAsImage', false);
         lastRenderArgs = {
            editorPath: data.sourceFilePath,
            sceneName: data.sceneName,
            quality: data.currentQuality, // The quality that was attempted
            isImage: renderAsImage
        };
    }


    let mediaPathWebviewUri = '';
    if (data.outputFilePath && fs.existsSync(data.outputFilePath)) { // Check if file exists before creating URI
        const onDiskPath = vscode.Uri.file(data.outputFilePath);
        mediaPathWebviewUri = previewPanel.webview.asWebviewUri(onDiskPath).toString();
    } else if (data.outputFilePath) {
        // If outputFilePath is provided but doesn't exist, it's an issue.
        // This might have been caught earlier, but double-check.
        data.error = (data.error ? data.error + "\n" : "") + `Output file specified but not found: ${data.outputFilePath}`;
    }
    
    previewPanel.title = data.sceneName ? `Manim: ${data.sceneName}` : 'Manim Preview';
    previewPanel.webview.html = getWebviewContent(
        previewPanel.webview,
        mediaPathWebviewUri, // This will be empty if file not found or not specified
        data.outputType,
        data.renderTime,
        data.sceneName,
        data.outputFilePath, // Absolute path for display
        data.sourceFilePath, // Can be undefined for display purposes if error occurs before this is known
        data.error,
        data.currentQuality
    );
}


/**
 * Generates the HTML content for the webview.
 * @param webview The webview instance.
 * @param mediaPath The webview URI for the video/image file. Can be empty.
 * @param outputType Type of the output ('video' or 'image').
 * @param renderTime String representing the render duration.
 * @param sceneName Name of the rendered scene.
 * @param outputFilePath Absolute path to the rendered file for display.
 * @param sourceFilePath Absolute path to the source Python file for display.
 * @param error Optional error message to display.
 * @param currentQuality The quality that was used for rendering (e.g., 'h')
 * @returns HTML string.
 */
function getWebviewContent(
    webview: vscode.Webview,
    mediaPath: string, // Webview URI, can be empty string
    outputType?: 'video' | 'image' | 'unknown',
    renderTime?: string,
    sceneName?: string,
    outputFilePath?: string,
    sourceFilePath?: string,
    error?: string,
    currentQuality?: string
): string {
    const nonce = getNonce(); // For Content Security Policy

    const qualityOptions = [
        { value: 'l', text: 'Low (480p)' },
        { value: 'm', text: 'Medium (720p)' },
        { value: 'h', text: 'High (1080p)' },
        { value: 'k', text: 'Production (4K)' }
    ];

    let mediaElement = '';
    if (error) {
        mediaElement = `<div class="error-message"><h3>渲染错误</h3><p>${error.replace(/\n/g, '<br>')}</p></div>`;
    } else if (mediaPath && outputType === 'video') { // Check mediaPath is not empty
        mediaElement = `<video id="preview-video" controls autoplay loop src="${mediaPath}" style="width: 100%; max-height: 50vh; border-radius: 8px; background-color: #222;"></video>`;
    } else if (mediaPath && outputType === 'image') { // Check mediaPath is not empty
        mediaElement = `<img id="preview-image" src="${mediaPath}" alt="Rendered Scene" style="max-width: 100%; max-height: 70vh; display: block; margin: auto; border-radius: 8px; background-color: #f0f0f0;" />`;
    } else if (!mediaPath && !error && sceneName) { // If we have a scene name but no media (e.g. file not found post-render)
         mediaElement = `<p>已渲染场景 ${sceneName}，但无法找到或加载预览媒体文件。</p>`;
         if(outputFilePath) {
            mediaElement += `<p>预期文件路径: <span class="filepath">${outputFilePath}</span></p>`;
         }
    } else if (!sceneName && !error) { // Initial state or no scene rendered yet
        mediaElement = `<p>请先选择并渲染一个 Manim 场景。</p>`;
    } else if (outputType === 'unknown' && mediaPath) {
        mediaElement = `<p>无法预览此文件类型。文件路径: <a href="${mediaPath}">${mediaPath}</a></p>`;
    }


    return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Content-Security-Policy" 
          content="default-src 'none'; 
                   style-src ${webview.cspSource} 'unsafe-inline'; 
                   img-src ${webview.cspSource} vscode-resource: data: https:; 
                   script-src 'nonce-${nonce}'; 
                   media-src ${webview.cspSource} vscode-resource:;">
    <title>Manim 预览</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
            padding: 15px;
            color: var(--vscode-editor-foreground);
            background-color: var(--vscode-editor-background);
            display: flex;
            flex-direction: column;
            height: 100vh;
            box-sizing: border-box;
            margin: 0;
        }
        .preview-area {
            flex: 1; 
            min-height: 200px; 
            display: flex;
            justify-content: center;
            align-items: center;
            border: 1px solid var(--vscode-editorWidget-border, #454545);
            border-radius: 8px;
            margin-bottom: 15px;
            overflow: hidden; 
            background-color: var(--vscode-sideBar-background, #252526); /* Match editor bg */
        }
        .preview-area video, .preview-area img {
            background-color: transparent; /* Video/image background should be transparent if media area has bg */
        }
        .controls-info-area {
            flex-shrink: 0; 
            overflow-y: auto; 
            padding-top: 10px;
            border-top: 1px solid var(--vscode-editorWidget-border, #454545);
        }
        .info-grid {
            display: grid;
            grid-template-columns: auto 1fr;
            gap: 8px 12px;
            align-items: center;
            font-size: 0.9em;
        }
        .info-grid label {
            font-weight: normal; /* Less bold for a cleaner look */
            text-align: right;
            color: var(--vscode-descriptionForeground); /* Softer color for labels */
        }
        .info-grid span, .info-grid select, .info-grid .filepath {
            word-break: break-all;
        }
        select, button {
            padding: 6px 10px;
            border-radius: 4px;
            border: 1px solid var(--vscode-input-border, #3C3C3C);
            background-color: var(--vscode-input-background, #3C3C3C);
            color: var(--vscode-input-foreground, #CCCCCC);
            font-size: 0.9em;
        }
        select:focus, button:focus {
            outline: 1px solid var(--vscode-focusBorder);
        }
        button {
            cursor: pointer;
            margin-top: 10px;
            background-color: var(--vscode-button-background);
            color: var(--vscode-button-foreground);
            border: 1px solid var(--vscode-button-border, transparent);
        }
        button:hover {
            background-color: var(--vscode-button-hoverBackground);
        }
        .error-message {
            color: var(--vscode-errorForeground, #F48771);
            background-color: rgba(244, 135, 113, 0.05); /* Softer background */
            padding: 10px;
            border-radius: 4px;
            border: 1px solid var(--vscode-errorForeground, #F48771);
            font-size: 0.9em;
            width: 100%;
            box-sizing: border-box;
        }
        .error-message h3 {
            margin-top: 0;
            font-size: 1.1em;
        }
        .filepath {
            font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
            background-color: var(--vscode-textCodeBlock-background, rgba(128,128,128,0.1));
            padding: 3px 5px;
            border-radius: 3px;
            font-size: 0.85em;
            color: var(--vscode-editor-foreground); /* Ensure good contrast */
        }
    </style>
</head>
<body>
    <div class="preview-area">
        ${mediaElement}
    </div>

    <div class="controls-info-area">
        <div class="info-grid">
            <label for="quality-select">清晰度:</label>
            <select id="quality-select" title="选择渲染清晰度并重新渲染">
                ${qualityOptions.map(opt => `<option value="${opt.value}" ${opt.value === currentQuality ? 'selected' : ''}>${opt.text}</option>`).join('')}
            </select>

            <label>渲染耗时:</label>
            <span id="render-time">${renderTime || 'N/A'}</span>

            <label>场景名称:</label>
            <span id="scene-name">${sceneName || 'N/A'}</span>
            
            <label>输出文件:</label>
            <span id="output-file-path" class="filepath" title="${outputFilePath || ''}">${outputFilePath ? path.basename(outputFilePath) : 'N/A'}</span>
            
            <label>代码路径:</label>
            <span id="source-file-path" class="filepath" title="${sourceFilePath || ''}">${sourceFilePath ? path.basename(sourceFilePath) : 'N/A'}</span>
        </div>
        ${sceneName ? '<button id="rerender-button" title="使用当前选择的清晰度重新渲染此场景">重新渲染当前场景</button>' : ''}
    </div>

    <script nonce="${nonce}">
        // VS Code API for communication
        const vscode = acquireVsCodeApi();

        // It's good practice to check if elements exist before adding listeners,
        // though in this controlled HTML they should.
        const qualitySelect = document.getElementById('quality-select');
        if (qualitySelect) {
            qualitySelect.addEventListener('change', (event) => {
                vscode.postMessage({
                    command: 'qualityChanged',
                    quality: event.target.value
                });
            });
        }

        const rerenderButton = document.getElementById('rerender-button');
        if (rerenderButton) {
            rerenderButton.addEventListener('click', () => {
                vscode.postMessage({ command: 'rerenderRequested' });
            });
        }
        
        // Expose path.basename to the script if needed for display, or do it in JS part of getWebviewContent
        // For example, to display only basename for file paths in the UI:
        // const outputFilePathSpan = document.getElementById('output-file-path');
        // if (outputFilePathSpan && outputFilePathSpan.textContent && outputFilePathSpan.textContent !== 'N/A') {
        //    // This logic is now directly in the HTML template for simplicity
        // }
        // const sourceFilePathSpan = document.getElementById('source-file-path');
        // if (sourceFilePathSpan && sourceFilePathSpan.textContent && sourceFilePathSpan.textContent !== 'N/A') {
        //    // This logic is now directly in the HTML template for simplicity
        // }

    </script>
</body>
</html>`;
}

/**
 * Generates a nonce for Content Security Policy in webviews.
 * @returns A random string.
 */
function getNonce() {
    let text = '';
    const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    for (let i = 0; i < 32; i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length));
    }
    return text;
}

/**
 * This function is called when your extension is deactivated.
 */
export function deactivate() {
    if (previewPanel) {
        previewPanel.dispose();
    }
}

